#include "precompiled.hpp"
#include "saved_rules.hpp"
#include "exec_context.hpp"
#include "detail/stack_allocator.hpp"

#define MAX_TOTAL_ARGS 24

saved_rule::saved_rule(jam_context *ctx, jam_module *module,
		   setcomp_node_const_ptr node, const list_of_lists &args) :
	jam_rule(ctx,module), node_(node), total_args_()
{
	for(size_t f=0,fend=args.size();f!=fend;++f)
	{
		const jam_list &cur=args[f];
		size_t sz=cur.size();

		//TODO: document empty argument errors. It's different from BJam's		
		if (sz==0 || cur.front().empty()) argument_error("Empty argument",args);	

		if (sz==1)
		{
			const jam_string &sec=cur.front();
			if (sec.size()==1 && sec[0]=='*')
				add_param(vararg,jam_string(boost::cref("unnamed"),7));
			else
				add_param(plain,cur.front());
			total_args_++;
			continue;
		}
		if (sz==2)
		{
			const jam_string &sec=*(++cur.begin());
			size_t sec_sz=sec.size();
			if (sec_sz==0) argument_error("Empty argument",args);
			if (sec_sz==1 && sec[0]=='+')
			{
				add_param(one_or_more,cur.front());
				total_args_++;
				continue;
			}
			if (sec_sz==1 && sec[0]=='*')
			{
				add_param(vararg,cur.front());
				total_args_++;
				continue;
			}			
		}
		
		//It's a sublist parameter. Parse it.
		size_t opt_count=0;
		bool must_be_optional=false;
		bool opt_sign_pos=true; //Set to true if '?' sign flag is expected
		param_desc &sub=add_list_param();
		for(jam_list::const_iterator f=cur.begin(),fend=cur.end();f!=fend;)
		{
			const jam_string &val=*f;
			size_t val_sz=val.size();
			bool is_opt=(val_sz==1 && val[0]=='?');
			if (val_sz==0) argument_error("Empty argument",args);

			//Check for the last vararg parameter. 			
			++f; //NOTE: THIS OPERATOR IS CYCLE INCREMENT.
			if (f==fend && val.size()==1 && val[0]=='*')
			{
				sub.last_subparam_vararg_=true;
				break;
			}

			if (!is_opt && must_be_optional && opt_sign_pos)
				//TODO: document this behaviour. It's different from BJam's
				argument_error("Optional argument expected",args);

			if (!is_opt)
				sub.sublist_.push_back(val);
			else
				must_be_optional=is_opt;
			
			if (must_be_optional) 
			{
				//Count optional arguments (by their '?' signs).
				if (opt_sign_pos) ++opt_count;
				//Do '?' sign flag flipping only when parsing optional list
				opt_sign_pos=!opt_sign_pos;
			}
		}
		if (must_be_optional && opt_sign_pos)
			argument_error("Optional argument expected",args);

		sub.num_sub_opt_=opt_count;
		total_args_+=sub.sublist_.size();
	}
}

void saved_rule::argument_error(const char* message, const list_of_lists &args)
{		
	context_->err()<<"*** argument error"<<std::endl;
	context_->err()<<"* rule "<<node_->rule_name_<<" ( ";
	context_->err()<<args;
	context_->err()<<" )"<<std::endl;
	context_->err()<<"* "<<message<<std::endl;
	context_->err()<<node_->file_<<":"<<node_->line_<<": ";
	context_->err()<<"see definition of rule '"<<node_->rule_name_<<"'."<<std::endl;	
	throw jam_exception("Interpreter exception");
}

void saved_rule::do_invoke(jam_frame *frame,jam_list *res)
{	
	fixed_size_list<var_saver,MAX_TOTAL_ARGS> savers;

	//Set the values of formal parameters
	const list_of_lists &args=frame->args_;
	size_t f=0,fend=args.size();
	
	for(;f!=fend;++f)
	{
		if (params_[f].type_!=sublist)
		{
			const jam_string &name=params_[f].name_;
			jam_list &var_ref=frame->module_->get_var_value_ref(name);
			savers.push_back().set_val(&var_ref);
			var_ref=frame->args_[f];
		} else
		{
			const jam_list &formal=params_[f].sublist_;
			const jam_list &actual=frame->args_[f];

			jam_list::const_iterator act=actual.begin(), actend=actual.end(),
				fnend=formal.end(), fn=formal.begin();

			for(;fn!=fnend;++fn)				
			{				
				const jam_string &name=*fn;
				jam_list &var_ref=frame->module_->get_var_value_ref(name);
				savers.push_back().set_val(&var_ref);
				if (act!=actend)
				{
					var_ref=*act;
					++act;
				} else
					var_ref.clear();
			}
			assert(params_[f].last_subparam_vararg_ || act==actend);
			//Process the last vararg parameter
			if (params_[f].last_subparam_vararg_)
			{
				//TODO: maybe optimize this?
				jam_list &var_ref=frame->module_->get_var_value_ref(formal.back());
				for(;act!=actend;++act)
					var_ref.push_back(*act);
			}

		}
	}

	//Caller must setup the appropriate frame		
	context_->intepret_tree(node_->rule_content_tree_,frame->module_,frame,res);
}
